package com.simple.crawler.utils;

import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class ReflectUtils {

    private static final Logger logger = LoggerFactory.getLogger(ReflectUtils.class);

    public static <T> T newInstance(Class<T> c) {
        try {
            return c.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            logger.error("创建实例失败:{}, {}", c.getName(), e);
        }
        return null;
    }

    public static Object getFieldValue(Object obj, Field field) {
        try {
            return field.get(obj);
        } catch (Exception e) {
            return null;
        }
    }

    public static List<Field> getFields(Class<?> c, List<Field> fieldList) {
        return getFields(c, null, fieldList);
    }

    public static List<Field> getFields(Class<?> c, Class<? extends Annotation> filterType, List<Field> fieldList) {
        fieldList.addAll(Arrays.asList(c.getDeclaredFields()).stream().
                filter(field -> filterType == null ? true : field.getAnnotation(filterType) != null).
                collect(Collectors.toList())
        );
        if (c.getSuperclass() != null) {
            getFields(c.getSuperclass(), filterType, fieldList);
        }
        for (Class<?> iClass: c.getInterfaces()) {
            getFields(iClass, filterType, fieldList);
        }
        return fieldList;
    }



    public static <T extends Annotation> Map<Field, T> getFields(Class<?> c, Class<T> filterType, Map<Field, T> fieldMap) {
        fieldMap.putAll(Arrays.asList(c.getDeclaredFields()).stream().
                filter(field -> filterType == null ? true : field.getAnnotation(filterType) != null).
                collect(Collectors.toMap(field -> field, field -> field.getAnnotation(filterType))
                )
        );
        if (c.getSuperclass() != null) {
            getFields(c.getSuperclass(), filterType, fieldMap);
        }
        for (Class<?> iClass: c.getInterfaces()) {
            getFields(iClass, filterType, fieldMap);
        }
        return fieldMap;
    }

    public static Field getField(Class<?> c, String fieldName) {
        try {
            return c.getDeclaredField(fieldName);
        } catch (Exception e) {
            if (c.getSuperclass() == null) {
                return null;
            }
            return getField(c.getSuperclass(), fieldName);
        }
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) {
        try {
            Field field = getField(obj.getClass(), fieldName);
            if (field == null) {
                throw new RuntimeException("未找到该字段");
            }
            field.setAccessible(true);
            field.set(obj, value);
        } catch (Exception e) {
            logger.error("set field value fail:{},{},{}", obj.getClass().getName(), fieldName, e);
        }
    }

    public static <T> T getInstance(Class<T> c, Object... args) {
        try {
            Constructor<T> constructor = c.getConstructor(Arrays.asList(args).stream().map(arg -> arg.getClass()).
                    toArray( arg -> new Class<?>[args.length])
            );
            return constructor.newInstance(args);
        } catch (Exception e) {
            logger.error("create instance fail:{}, {}", c.getName(), e);
        }
        return null;
    }

    public static Method getMethod(Object obj, String methodName, Object[] args) throws Exception {
        return obj.getClass().getMethod(methodName,  Arrays.asList(args).stream().map(
                arg -> arg.getClass()).
                toArray( arg -> new Class<?>[args.length])
        );
    }

    public static Method getDeclaredMethod(Object obj, String methodName, Object[] args) throws Exception {
        return obj.getClass().getDeclaredMethod(methodName,  Arrays.asList(args).stream().map(
                arg -> arg.getClass()).
                toArray( arg -> new Class<?>[args.length])
        );
    }

    public static <T> T invoke(Object obj, String methodName, Object[] args, Class<T> resultType) throws Exception {
        Method method = obj.getClass().getDeclaredMethod(methodName,  Arrays.asList(args).stream().map(
                arg -> arg.getClass()).
                toArray( arg -> new Class<?>[args.length])
        );
        return invoke(obj, method, args, resultType);
    }

    public static <T> T invoke(Object obj, Method method, Object[] args, Class<T> resultType) throws Exception {
        method.setAccessible(true);
        return (T) method.invoke(obj, args);
    }

    public static <T extends Annotation> T getAnnotation(Class<?> type, Class<T> resultType) {
        T annotation = type.getDeclaredAnnotation(resultType);
        if (annotation == null && type.getSuperclass() != null) {
            return getAnnotation(type.getSuperclass(), resultType);
        } else if (annotation == null && type.getInterfaces() != null) {
            for (Class<?> c: type.getInterfaces()) {
                return getAnnotation(c, resultType);
            }
        }
        return annotation;
    }

    public static <T> Set<Class<? extends T>> getClassList(String pkg, Class<T> c) {
        return new Reflections(pkg).getSubTypesOf(c);
    }

    public static Class<?> getListActualType(Field listField) {
        try {
            ParameterizedType listGenericType = (ParameterizedType) listField.getGenericType();
            Type[] listActualTypeArguments = listGenericType.getActualTypeArguments();
            return Class.forName(listActualTypeArguments[0].getTypeName());
        } catch (Exception e) {

        }
        return null;
    }

    public static Class<?> getClassActualType(Class<?> c) {
        ParameterizedType type= (ParameterizedType) c.getGenericSuperclass();
        Type[] actualTypeArguments = type.getActualTypeArguments();
        if (actualTypeArguments == null || actualTypeArguments.length == 0) {
            return null;
        }
        return (Class<?>) actualTypeArguments[0];
    }

}
